package org.jiamandan.com.config;

import cn.hutool.core.collection.CollectionUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

/**
 * @version Revision 1.0.1
 * @版权：版权所有 (c) 2021
 * @see:
 * @创建日期：2022-10-21 15:24
 * @功能说明：
 * @begin
 * @修改记录:
 * @修改后版本 修改人        修改内容
 * @2022-10-21 15:24	         Lee        	创建
 * @end
 */
@Slf4j
@Component
@EnableConfigurationProperties({
        NettyProperties.class
})
public class NettyServer {

    @Resource
    private NettyInitializer socketInitializer;
    @Getter
    private ServerBootstrap serverBootstrap;
    @Resource
    private NettyProperties socketProperties;

    /**
     * 启动netty服务器
     */
    public void start() {
        this.init();
        this.serverBootstrap.bind(socketProperties.getPort());
//        this.serverBootstrap.bind(18090);
        log.info("Netty started on port: {} (TCP) with boss thread {}", socketProperties.getPort(), socketProperties.getBossThread());
    }

    /**
     * 初始化netty配置
     */
    private void init() {
        // 创建两个线程组，bossGroup为接收请求的线程组，一般1-2个就行
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(socketProperties.getBossThread());
        // 实际工作的线程组
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(socketProperties.getBossThread() * 2);
        this.serverBootstrap = new ServerBootstrap();
        this.serverBootstrap.group(bossGroup, workerGroup) // 两个线程组加入进来
                .channel(NioServerSocketChannel.class)  // 配置为nio类型
                //拆包
//                .childOption(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator(64, 64, 1024))
                .childHandler(this.socketInitializer); // 加入自己的初始化器
    }


    public void startNettyServer(List<Integer> ports) {

        /*
         * 配置服务端的NIO线程组
         * NioEventLoopGroup 是用来处理I/O操作的Reactor线程组
         * bossGroup：用来接收进来的连接，workerGroup：用来处理已经被接收的连接,进行socketChannel的网络读写，
         * bossGroup接收到连接后就会把连接信息注册到workerGroup
         * workerGroup的EventLoopGroup默认的线程数是CPU核数的二倍
         */
        EventLoopGroup bossGroup = new NioEventLoopGroup(socketProperties.getBossThread());
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            // ServerBootstrap 是一个启动NIO服务的辅助启动类
            ServerBootstrap serverBootstrap = new ServerBootstrap();

            // 设置group，将bossGroup， workerGroup线程组传递到ServerBootstrap
            serverBootstrap = serverBootstrap.group(bossGroup, workerGroup);

            // ServerSocketChannel是以NIO的selector为基础进行实现的，用来接收新的连接，这里告诉Channel通过NioServerSocketChannel获取新的连接
//            serverBootstrap = serverBootstrap.channel(NioServerSocketChannel.class);

            //netty提供的ByteBuf支持容量动态调整，对于接收缓冲区的内存分配器，Netty提供了两种：
            //FixedRecvByteBufAllocator：固定长度的接收缓冲区分配器，由它分配的ByteBuf长度都是固定大小的，并不会根据实际数据报的大小动态收缩。但是，如果容量不足，支持动态扩展。动态扩展是Netty ByteBuf的一项基本功能，与ByteBuf分配器的实现没有关系；
            //AdaptiveRecvByteBufAllocator：容量动态调整的接收缓冲区分配器，它会根据之前Channel接收到的数据报大小进行计算，如果连续填充满接收缓冲区的可写空间，则动态扩展容量。如果连续2次接收到的数据报都小于指定值，则收缩当前的容量，以节约内存
            serverBootstrap = serverBootstrap.channel(NioServerSocketChannel.class)
                    // Netty默认不使用内存池，需要在创建客户端或者服务端的时候进行指定
                    //.option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

            // 初始化通道，主要用于网络I/O事件，记录日志，编码、解码消息
            serverBootstrap = serverBootstrap.childHandler(new NettyChannelInitializer());

            // 绑定端口，同步等待成功
            ChannelFuture[] channelFutures;
            ChannelFuture futureTcp;
            if (CollectionUtil.isNotEmpty(ports)) {
                channelFutures = new ChannelFuture[ports.size()];
                int i = 0;
                for (Integer port : ports) {
                    // 绑定端口，同步等待成功 绑定的服务器
                    futureTcp = serverBootstrap.bind(port).sync();
                    channelFutures[i++] = futureTcp;
                    futureTcp.addListener(future -> {
                        if (future.isSuccess()) {
                            System.out.println("netty server 启动成功!" + port);
                        } else {
                            System.out.println("netty server 启动失败!" + port);
                        }
                    });
                }
                for (ChannelFuture future : channelFutures) {
                    // 等待服务器监听端口关闭
                    future.channel().closeFuture().sync();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 退出，释放线程池资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
